define(['fastdom'], function (fastdom) {

    var $md = {};

    /*
     * Shims
     */

    var getIE = function() {
        //var v = root.$IE = parseInt((navigator.userAgent.split("MSIE ")[1] || "0").charAt(0));
        //return root.$IE || (isNaN(v) ? "0" : v);

        var MSIEs = navigator.userAgent.split('MSIE ')[1] || "0";
        var DNETs = navigator.userAgent.split('rv:')[1] || "0";

        MSIEs = MSIEs.split(".")[0];
        DNETs = DNETs.split(".")[0];

        var msie = ~~MSIEs;
        var dnet = ~~DNETs;

        if (msie != 0) {
            return msie;
        }
        if (dnet != 0) {
            return dnet;
        }

        return 0;
    };

    var H = {
        getIE: getIE,
    };

    var animatedImageFormat = 'gif';

    function canUseWebP() {
        var elem = document.createElement('canvas');

        if (!!(elem.getContext && elem.getContext('2d'))) {
            // was able or not to get WebP representation
            return elem.toDataURL('image/webp').indexOf('data:image/webp') == 0;
        }

        // very old browser like IE 8, canvas not supported
        return false;
    }

    function determineAnimatedImageFormat() {
        if (canUseWebP()) {
            animatedImageFormat = 'webp';
        } else if (H.getIE() > 0) {
            //stick to gif
        } else {
            //if APNG is supported
            animatedImageFormat = 'png';
        }
    }

    determineAnimatedImageFormat();

    // by default, css/dom updates will be executed asynchrously
    // still we can specify `sync` flag to force it to be executed synchronously
    function addClassToElem(elem, clz, sync) {
        if (!elem) return;
        var currClz = elem.getAttribute('class') || '';
        var alone = currClz.split(' ');
        if (alone.indexOf(clz) > -1) {
            return;
        }
        requestDomUpdate(function () {
            clz.split(' ').forEach(function (c) {
                if (c && c.length) {
                    elem.classList.add(c);
                }
            });
        }, sync);
    }

    function removeClassFromElem(elem, clz, sync) {
        if (!elem) return;
        var currClz = elem.getAttribute('class') || '';
        var alone = currClz.split(' ');
        var currClzIdx = alone.indexOf(clz);
        if (currClzIdx > -1) {
            alone.splice(currClzIdx, 1);
            requestDomUpdate(function () {
                clz.split(' ').forEach(function (c) {
                    if (c && c.length) {
                        elem.classList.remove(c);
                    }
                });
            }, sync);
        }
    }

    function getAncestorOfClass(elem, selector) {
        var curr = elem;
        if (!selector) return curr;
        do {
            if (curr) {
                if (curr.matches(selector)) {
                    return curr;
                }
                if (curr === curr.parentElement) return;
                curr = curr.parentElement;
            }
        } while (curr);
        return curr;
    }

    function setInnerHtml(el, child, sync) {
        if (!el) return;
        requestDomUpdate(function () {
            if (typeof child === 'string') {
                el.innerHTML = child;
            } else {
                while (el.firstChild) el.removeChild(el.firstChild);
                el.appendChild(child);
            }
        }, sync);
    }

    function setElemVisible(elem, visible, sync) {
        if (!elem) return;
        if (visible === undefined) {
            visible = true;
        }
        requestDomUpdate(function () {
            elem.style.display = visible ? 'block' : 'none';
        }, sync);
    }

    function requestDomUpdate(func, sync) {
        if (window.fastdom && !sync) {
            window.fastdom.mutate(func);
        } else {
            func && func();
        }
    }

    $md.animatedImageFormat = animatedImageFormat;
    $md.addClassToElem = addClassToElem;
    $md.removeClassFromElem = removeClassFromElem;
    $md.getAncestorOfClass = getAncestorOfClass;
    $md.setInnerHtml = setInnerHtml;
    $md.setElemVisible = setElemVisible;
    $md.requestDomUpdate = requestDomUpdate;

    return $md;

});